﻿using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Web;

namespace Meotom.Upload.v2.Services
{
    /// <summary>
    /// Summary description for HttpCombiner
    /// </summary>
    public class HttpCombiner : IHttpHandler
    {
        private const bool DoGzip = true;
        private readonly static TimeSpan CacheDuration = TimeSpan.FromDays(30);

        public void ProcessRequest(HttpContext context)
        {
            var request = context.Request;

            // Read setName, contentType and version. All are required. They are
            // used as cache key
            var setName = request["s"] ?? string.Empty;
            var contentType = request["t"] ?? string.Empty;
            var version = request["v"] ?? string.Empty;

            // Decide if browser supports compressed response
// ReSharper disable RedundantLogicalConditionalExpressionOperand
            var isCompressed = DoGzip && CanGZip(context.Request);
// ReSharper restore RedundantLogicalConditionalExpressionOperand

            // Response is written as UTF8 encoding. If you are using languages like
            // Arabic, you should change this to proper encoding 

            // If the set has already been cached, write the response directly from
            // cache. Otherwise generate the response and cache it
            if (WriteFromCache(context, setName, version, isCompressed, contentType)) return;
            using (var memoryStream = new MemoryStream(5000))
            {
                // Decide regular stream or GZipStream based on whether the response
                // can be cached or not
                using (var writer = isCompressed ?
                                                        (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) :
                                                                                                                             memoryStream)
                {

                    // Load the files defined in <appSettings> and process each file
                    var setDefinition =
                        System.Configuration.ConfigurationManager.AppSettings[setName] ?? "";
                    var fileNames = setDefinition.Split(new[] { ',' },
                                                             StringSplitOptions.RemoveEmptyEntries);

                    foreach (var fileBytes in
                        fileNames.Select(fileName => GetFileBytes(context, fileName.Trim())))
                    {
                        writer.Write(fileBytes, 0, fileBytes.Length);
                    }

                    writer.Close();
                }

                // Cache the combined response so that it can be directly written
                // in subsequent calls 
                var responseBytes = memoryStream.ToArray();
                context.Cache.Insert(GetCacheKey(setName, version, isCompressed),
                                     responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
                                     CacheDuration);

                // Generate the response
                WriteBytes(responseBytes, context, isCompressed, contentType);
            }
        }

        private static byte[] GetFileBytes(HttpContext context, string virtualPath)
        {
            if (virtualPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
            {
                using (var client = new WebClient())
                {
                    return client.DownloadData(virtualPath);
                }
            }

            var physicalPath = context.Server.MapPath(virtualPath);
            var bytes = File.ReadAllBytes(physicalPath);
            // TODO: Convert unicode files to specified encoding. For now, assuming
            // files are either ASCII or UTF8
            return bytes;
        }

        private static bool WriteFromCache(HttpContext context, string setName, string version,
            bool isCompressed, string contentType)
        {
            var responseBytes = context.Cache[GetCacheKey(setName, version, isCompressed)] as byte[];

            if (null == responseBytes || 0 == responseBytes.Length) return false;

            WriteBytes(responseBytes, context, isCompressed, contentType);
            return true;
        }

        private static void WriteBytes(byte[] bytes, HttpContext context,
            bool isCompressed, string contentType)
        {
            var response = context.Response;

            response.AppendHeader("Content-Length", Convert.ToString(bytes.Length));
            response.ContentType = contentType;
            if (isCompressed)
                response.AppendHeader("Content-Encoding", "gzip");

            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.Now.Add(CacheDuration));
            context.Response.Cache.SetMaxAge(CacheDuration);
            context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
            //context.Response.StatusCode = 304;
            //context.Response.Status = "404 Not Modify";
            //context.Response.StatusCode = 403;
            response.OutputStream.Write(bytes, 0, bytes.Length);
            response.Flush();
        }

        private static bool CanGZip(HttpRequest request)
        {
            var acceptEncoding = request.Headers["Accept-Encoding"];
            return !string.IsNullOrEmpty(acceptEncoding) &&
                   (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"));
        }

        private static string GetCacheKey(string setName, string version, bool isCompressed)
        {
            return "HttpCombiner." + setName + "." + version + "." + isCompressed;
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}